/*

Copyright (C) 2002 - 2008 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <nd.h>
#include <nd_gui.h>
#include <nd_dialog.h>

#include <nd_icmp.h>
#include <nd_icmp_callbacks.h>


void    
nd_icmp_type_cb(LND_Packet   *packet,
		guchar      *header,
		guchar      *data)
{
  static GtkWidget *menu = NULL;

  if (!menu)
    menu = nd_gui_create_menu(icmp_menu_type_data);
  
  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, 0);
  
  return;
  TOUCH(header);
  TOUCH(packet);  
  TOUCH(data);
}


void    
nd_icmp_code_cb(LND_Packet   *packet,
		guchar      *header,
		guchar      *data)
{
  static GtkWidget *unreach_menu = NULL;
  static GtkWidget *redirect_menu = NULL;

  struct icmp *icmphdr = (struct icmp *) header;
  
  switch (icmphdr->icmp_type)
    {
    case ICMP_DEST_UNREACH:
      if (!unreach_menu)
	unreach_menu = nd_gui_create_menu(icmp_menu_unreach_code_data);
      gtk_menu_popup(GTK_MENU(unreach_menu), NULL, NULL, NULL, NULL, 0, 0);
      return;

    case ICMP_REDIRECT:
      if (!redirect_menu)
	redirect_menu = nd_gui_create_menu(icmp_menu_redirect_code_data);
      gtk_menu_popup(GTK_MENU(redirect_menu), NULL, NULL, NULL, NULL, 0, 0);
      return;

    default:
      nd_dialog_number(_("Enter ICMP code:"),
		       ND_BASE_DEC,
		       icmphdr->icmp_code,
		       255,
		       (ND_NumberCallback) nd_icmp_code_value_cb,
		       NULL,
		       packet, header);
    }
    
  return;
  TOUCH(header);
  TOUCH(packet);  
  TOUCH(data);
}


static void
icmp_sum_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
  
      icmphdr->icmp_cksum = htons(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_cksum_cb(LND_Packet   *packet,
		 guchar      *header,
		 guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP checksum:"),
		   ND_BASE_HEX,
		   ntohs(icmphdr->icmp_cksum), 65535,
		   icmp_sum_ok_cb,
		   NULL,
		   packet, data);
  
}


static void
icmp_id_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
  
      switch (icmphdr->icmp_type)
	{
	case ICMP_ECHO:
	case ICMP_ECHOREPLY:
	case ICMP_ADDRESS:
	case ICMP_ADDRESSREPLY:
	case ICMP_TIMESTAMP:
	case ICMP_TIMESTAMPREPLY:
	case ICMP_INFO_REQUEST:
	case ICMP_INFO_REPLY:
	  break;
	  
	default:
	  continue;
	}
      
      icmphdr->icmp_id = value;
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_id_cb(LND_Packet   *packet,
	      guchar      *header,
	      guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP identifier:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_id,
		   65535,
		   (ND_NumberCallback) icmp_id_ok_cb,
		   NULL,
		   packet, header);

  return;
  TOUCH(data);
}


static void
icmp_seq_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
  
      switch (icmphdr->icmp_type)
	{
	case ICMP_ECHO:
	case ICMP_ECHOREPLY:
	case ICMP_ADDRESS:
	case ICMP_ADDRESSREPLY:
	case ICMP_TIMESTAMP:
	case ICMP_TIMESTAMPREPLY:
	case ICMP_INFO_REQUEST:
	case ICMP_INFO_REPLY:
	  break;
	  
	default:
	  continue;
	}
      
      icmphdr->icmp_seq = value;
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_seq_cb(LND_Packet   *packet,
	       guchar      *header,
	       guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP sequence number:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_seq,
		   65535,
		   (ND_NumberCallback) icmp_seq_ok_cb,
		   NULL,
		   packet, header);

  return;
  TOUCH(data);
}


static void 
icmp_ip_ok_cb(guchar     *address,
	      int         address_len,
	      LND_Packet *packet,
	      void       *user_data)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
  
      if (icmphdr->icmp_type != ICMP_ADDRESS &&
	  icmphdr->icmp_type != ICMP_ADDRESSREPLY)
	continue;
  
      memcpy(&icmphdr->icmp_gwaddr, address, sizeof(guint32));
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
  TOUCH(address_len);
}


void    
nd_icmp_ip_cb(LND_Packet   *packet,
	      guchar      *header,
	      guchar      *data)
{
  nd_dialog_ip(_("Enter IP subnet mask:"),
	       (guchar*) data,
	       icmp_ip_ok_cb,
	       NULL,
	       packet, data);  

  return;
  TOUCH(header);
}


static void
icmp_ts_orig_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
  
      if (icmphdr->icmp_type != ICMP_TIMESTAMP &&
	  icmphdr->icmp_type != ICMP_TIMESTAMPREPLY)
	continue;
      
      icmphdr->icmp_otime = htonl(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_ts_orig_cb(LND_Packet   *packet,
		   guchar      *header,
		   guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP receive timestamp:"),
		   ND_BASE_DEC,
		   ntohl(icmphdr->icmp_otime), (guint) -1,
		   icmp_ts_orig_ok_cb,
		   NULL, packet, data);  
}


static void
icmp_ts_recv_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != ICMP_TIMESTAMP &&
	  icmphdr->icmp_type != ICMP_TIMESTAMPREPLY)
	continue;

      icmphdr->icmp_rtime = htonl(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}



void    
nd_icmp_ts_recv_cb(LND_Packet   *packet,
		   guchar      *header,
		   guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP receive timestamp:"),
		   ND_BASE_DEC,
		   ntohl(icmphdr->icmp_rtime), (guint) -1,
		   icmp_ts_recv_ok_cb,
		   NULL, packet, data);  
}


static void
icmp_ts_trans_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != ICMP_TIMESTAMP &&
	  icmphdr->icmp_type != ICMP_TIMESTAMPREPLY)
	continue;
      
      icmphdr->icmp_ttime = htonl(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_ts_trans_cb(LND_Packet   *packet,
		    guchar      *header,
		    guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP transmit timestamp:"),
		   ND_BASE_DEC,
		   ntohl(icmphdr->icmp_ttime), (guint) -1,
		   icmp_ts_trans_ok_cb,
		   NULL, packet, data);  
}


static void
icmp_error_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      icmphdr->icmp_gwaddr.s_addr = htonl(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_error_cb(LND_Packet   *packet,
		 guchar      *header,
		 guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  if (!libnd_icmp_header_is_error(icmphdr) && icmphdr->icmp_type != 10)
    return;

  nd_dialog_number(_("Enter 32-bit ICMP data:"),
		   ND_BASE_DEC,
		   ntohl(icmphdr->icmp_gwaddr.s_addr), (guint) -1,
		   icmp_error_ok_cb,
		   NULL, packet, data);    
}


static void
icmp_adv_num_addr_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != 9)
	continue;

      icmphdr->icmp_num_addrs = value;
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_adv_num_addr_cb(LND_Packet   *packet,
			guchar      *header,
			guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter number of addresses in ICMP router advertisement:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_num_addrs, 255,
		   icmp_adv_num_addr_ok_cb,
		   NULL, packet, data);      
}


static void
icmp_adv_addr_entry_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != 9)
	continue;

      icmphdr->icmp_wpa = value;
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_adv_addr_entry_cb(LND_Packet   *packet,
			  guchar      *header,
			  guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP router advertisement entry size:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_wpa, 255,
		   icmp_adv_addr_entry_ok_cb,
		   NULL, packet, data);    
}


static void
icmp_adv_lifetime_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != 9)
	continue;

      icmphdr->icmp_lifetime = htons(value);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
}


void    
nd_icmp_adv_lifetime_cb(LND_Packet   *packet,
			guchar      *header,
			guchar      *data)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP router advertisement lifetime:"),
		   ND_BASE_DEC,
		   ntohs(icmphdr->icmp_lifetime), 65535,
		   icmp_adv_lifetime_ok_cb,
		   NULL, packet, data);    
}


static void
icmp_adv_ip_ok_cb(guchar      *address,
		  int          address_len,
		  LND_Packet  *packet,
		  void        *user_data)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != 9)
	continue;

      memcpy(address + 8 + GPOINTER_TO_INT(user_data) * 8, address, 4);
      libnd_packet_modified(libnd_pit_get(&pit));
    }
  
  return;
  TOUCH(user_data);
  TOUCH(address_len);
}


void    
nd_icmp_adv_ip_cb(LND_Packet  *packet,
		  guchar      *header,
		  guchar      *data)
{
  int entry_num = (data - header - 8) / 8;

  nd_dialog_ip(_("Enter router IP address:"),
	       (guchar*) data,
	       icmp_adv_ip_ok_cb,
	       NULL,
	       packet, GINT_TO_POINTER(entry_num));
}


static void
icmp_adv_pref_ok_cb(LND_Packet *packet, void *user_data, guint value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (icmphdr->icmp_type != 9)
	continue;

      memcpy((guchar *)icmphdr + 12 + GPOINTER_TO_INT(user_data) * 8,
	     &value, sizeof(guint32));
      libnd_packet_modified(libnd_pit_get(&pit));
    }
}


void    
nd_icmp_adv_pref_cb(LND_Packet   *packet,
		    guchar      *header,
		    guchar      *data)
{
  guint32 pref;
  int     entry_num;

  if (!data)
    return;

  entry_num = (data - header - 12) / 8;
  pref = * ((guint32*) data);

  nd_dialog_number(_("Enter ICMP router advertisement preference:"),
		   ND_BASE_DEC,
		   ntohl(pref), (guint) -1,
		   icmp_adv_pref_ok_cb,
		   NULL, packet, GINT_TO_POINTER(entry_num));    
}


/* Menu item callbacks */

void    
nd_icmp_type_value_cb(LND_Packet   *packet,
		      guchar       *header,
		      int           value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      icmphdr->icmp_type = value;
      libnd_packet_init(libnd_pit_get(&pit));
      libnd_packet_modified(libnd_pit_get(&pit));
    }

  return;
  TOUCH(header);
}


void    
nd_icmp_type_custom_cb(LND_Packet   *packet,
		       guchar      *header,
		       int          value)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP type:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_type,
		   255,
		   (ND_NumberCallback) nd_icmp_type_value_cb,
		   NULL,
		   packet, header);

  return;
  TOUCH(value);
}


void    
nd_icmp_code_value_cb(LND_Packet   *packet,
		      guchar      *header,
		      int          value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      icmphdr->icmp_code = value;
      libnd_packet_init(libnd_pit_get(&pit));
      libnd_packet_modified(libnd_pit_get(&pit));
    }

  return;
  TOUCH(header);
}


void    
nd_icmp_code_custom_cb(LND_Packet   *packet,
		       guchar      *header,
		       int          value)
{
  struct icmp *icmphdr = (struct icmp *) header;

  nd_dialog_number(_("Enter ICMP code:"),
		   ND_BASE_DEC,
		   icmphdr->icmp_code,
		   255,
		   (ND_NumberCallback) nd_icmp_code_value_cb,
		   NULL,
		   packet, header);

  return;
  TOUCH(value);
}


void    
nd_icmp_cksum_fix_cb(LND_Packet  *packet,
		     guchar      *header,
		     int          value)
{
  LND_Trace          *trace;
  LND_PacketIterator  pit;
  struct icmp        *icmphdr;
  guint16             correct_sum;

  if (! (trace = libnd_packet_get_trace(packet)))
    return;

  for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      icmphdr = (struct icmp *) libnd_packet_get_data(libnd_pit_get(&pit), nd_icmp_get(), 0);
      if (!icmphdr)
	continue;
      
      if (!libnd_icmp_csum_correct(libnd_pit_get(&pit), &correct_sum))
	{
	  icmphdr->icmp_cksum = correct_sum;
	  libnd_packet_modified(libnd_pit_get(&pit));
	}
    }

  return;
  TOUCH(header);
  TOUCH(value);
}


